﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NeuralNetworks.Learning
{
    class KohonenLearning
    {
        public LearningProperty LearningProperty { get; set; }


        public KohonenLearning(LearningProperty learningProperty)
        {
            LearningProperty.Conscience = learningProperty.Conscience;
            LearningProperty.Epochs = learningProperty.Epochs;
            LearningProperty.LearningRatio = learningProperty.LearningRatio;
            LearningProperty.NeighbourhoodDist = learningProperty.NeighbourhoodDist;
        }

        double Run(double[] input, double[] output)
        {
            return 0.0;
        }

        double RunEpoch(double[][] input, double[][] output)
        {
            return 0.0;
        }
#if SILVERLIGHT
        //neuron
        private void LearnNeuron(int winnerX, int winnerY, int epoch, double conscience, double learningFactor, double beta)
        {
            // Fi
            if (_x == winnerX && _y == winnerY)
            {
                _f = _f + beta * (1.0 - _f);
            }
            else
            {
                _f = _f + beta * (0.0 - _f);
            }
            // Bi
            _biasWeight = conscience * (_n * _f - 1.0);
            // Wij
            double dist = Math.Sqrt(EuclideanDist2(new double[] { _x, _y }, new double[] { winnerX, winnerY }));
            if (dist <= _neighbourhoodDist)
            {
                foreach (Connection c in _inputs)
                {
                    double gaussdist = Gauss(dist);
                    c.Weight = c.Weight + learningFactor * (c.N.Output - c.Weight) * gaussdist;
                }
            }
        }

        private double EuclideanDist2(double[] x, double[] y)
        {
            double sum = 0.0;
            double differ;
            for (int i = 0; i < x.Length; i++)
            {
                differ = x[i] - y[i];
                sum += differ * differ;
            }
            return sum;
        }

        private double VectorLength(double[] x)
        {
            double len = 0.0;
            for (int i = 0; i < x.Length; i++)
            {
                len += x[i] * x[i];
            }
            len = Math.Sqrt(len);
            return len;
        }
        private double[] NormalizeVector(double[] x)
        {
            double len = VectorLength(x);
            for (int i = 0; i < x.Length; i++)
            {
                x[i] = x[i] / len;
            }
            return x;
        }

        // stałe dla przyspieszenia liczenia funkcji Gaussa
        private const double Sigma = 0.4;
        private readonly double _divi = Sigma * (Math.Sqrt(2.0 * Math.PI));

        private double Gauss(double x)
        {
            return Math.Exp(x * x / (-2.0 * Sigma * Sigma)) / _divi;
        }


        //network
        virtual public void Learn(SortedList<int, LearningProperty> learningParams, int epochs)
        {

            LearningProperty currentProp = getCurrentProperty(learningParams);

            _kohonenLayer.NeighbourhoodDist = currentProp.NeighbourhoodDist;
            _kohonenLayer.LearningFactor = currentProp.LearningRatio;
            _kohonenLayer.Conscience = currentProp.Conscience;


            for (int i = 1; i <= epochs; i++)
            {

                for (int p = 0; p < _nPatterns; p++)
                {
                    // set pattern
                    _inputLayer.SetOutputs(_patterns[p]);

                    _kohonenLayer.Compute();

                    //int winI = _kohonenLayer.GetWinner();

                    _kohonenLayer.Learn(i);
                }
                _epochs++;
                int pIndex = learningParams.IndexOfKey(_epochs);
                if (pIndex > -1) //trzeba przelaczyc sie na nowe parametry
                {
                    try
                    {
                        currentProp = learningParams[learningParams.Keys[pIndex + 1]];
                        Console.WriteLine(currentProp);
                        _kohonenLayer.NeighbourhoodDist = currentProp.NeighbourhoodDist;
                        _kohonenLayer.LearningFactor = currentProp.LearningRatio;
                        _kohonenLayer.Conscience = currentProp.Conscience;
                    }
                    catch (ArgumentOutOfRangeException e)
                    {
                    }
                }
            }

        }

        protected LearningProperty getCurrentProperty(SortedList<int, LearningProperty> learningParams)
        {
            int paramIndex = 0;
            foreach (int e in learningParams.Keys)
            {
                paramIndex = e;
                if (_epochs <= e) break;
            }

            LearningProperty currentProp = learningParams[paramIndex];
            Console.WriteLine(currentProp);
            return currentProp;
        }


        //layer
        public void Learn(int epochs)
        {
            int winI = GetWinner();
            SomNeuron winner = Neurons[winI] as SomNeuron;
            int winX = winner.X;
            int winY = winner.Y;

            _epochs = epochs;
            foreach (SomNeuron neuron in Neurons)
            {
                neuron.Learn(winX, winY, _epochs, _conscience, _learningFactor, _beta);
            }
        }

        public void AddNeuron(SomNeuron neuron)
        {
            if (neuron == null) { throw (new NeuralNetworksException()); };
            Neurons.Add(neuron);
        }

        public double[] RealOutputs()
        {
            return base.Outputs();
        }

        public override double[] Outputs()
        {
            double[] result = new double[Neurons.Count];
            int winI = GetWinner();

            for (int i = 0; i < Neurons.Count; i++)
            {
                if (i == winI)
                {
                    result[i] = 1.0;
                }
                else
                {
                    result[i] = 0.0;
                }
            }
            return result;
        }

        public int GetWinner()
        {
            SomNeuron n = Neurons[0] as SomNeuron;
            double minimum = n.Output;
            int minI = 0;

            for (int i = 0; i < Neurons.Count; i++)
            {
                n = Neurons[i] as SomNeuron;
                if (n.Output < minimum)
                {
                    minI = i;
                    minimum = n.Output;
                }
            }
            return minI;
        }
#endif
    }
}
